﻿using System;
using System.Reflection;
using System.Security;
using PostSharp.Aspects;
using PostSharp.Extensibility;

namespace Theoreme.Teaching.AOP.Sample01
{
    [Serializable]
    public sealed class RequireRoleAttribute : OnMethodBoundaryAspect
    {
        private readonly string[] roles;
        private string parameterName;

        public RequireRoleAttribute( params string[] roles )
        {
            this.roles = roles;
            this.Parameter = -1;
        }


        public int Parameter { get; set; }
        

        public override bool CompileTimeValidate( MethodBase method )
        {
            Type securableType;

            if (this.Parameter < 0)
            {
                if ( method.IsStatic )
                {
                    Message.Write( method, SeverityType.Error, "MY001", "Error with [RequireRole] applied to {0}.{1}: the method cannot be static when the Parameter property is not set.",
                              method.DeclaringType.FullName, method.Name);
                    return false;
                }
                securableType = method.DeclaringType;

            }
            else
            {
                ParameterInfo[] parameters = method.GetParameters();
                if (this.Parameter >= parameters.Length)
                {
                    Message.Write( method, SeverityType.Error, "MY002", "Error with [RequireRole] applied to {0}.{1}: not enough parameters",
                              method.DeclaringType.FullName, method.Name);
                    return false;

                }

                securableType = parameters[this.Parameter].ParameterType;
                this.parameterName = parameters[this.Parameter].Name;

            }

            if ( !typeof(ISecurable).IsAssignableFrom( securableType ) )
            {
                Message.Write( method, SeverityType.Error, "MY003", "[RequireRole] cannot be applied to {0}.{1} because {2} does not implement ISecurable.",
                               method.DeclaringType.FullName, method.Name, securableType);
                return false;
            }

            return true;
        }

        public override void OnEntry( MethodExecutionArgs args )
        {
            ISecurable securable;
            
            if ( this.Parameter < 0 )
            { 
                securable = (ISecurable) args.Instance;
            }
            else
            {
                securable = (ISecurable) args.Arguments[this.Parameter];
                if ( securable == null )
                {
                    throw new ArgumentNullException( this.parameterName );
                }
            }

            foreach ( string role in this.roles )
            {
                if ( securable.IsInRole( UserContext.AuthenticatedUser, role ) )
                    return;
            }

            throw new SecurityException( string.Format( "User {0} must be in one of these roles: {1}.",
                                                        UserContext.AuthenticatedUser.Name, string.Join( ", ", this.roles ) ) );
        }
    }
}